package booter

import (
	"encoding/json"
	"fmt"
	"gluenet/pkg/apis"
	"gluenet/pkg/meta"
	"gluenet/pkg/platform/glue"
	v2 "gluenet/utils/rpc/v2/define"
	"gopkg.in/yaml.v3"
	"strconv"
	"strings"
	"sync"
	"time"
)

func runInstance(instance *meta.Instance, smaps map[string]string, rpc *apis.StorageContext) error {
	switch instance.Platform {
	case apis.PlatfromGluenets:
		var ins glue.Instance
		if err := json.Unmarshal([]byte(instance.InstanceValue), &ins); err != nil {
			return fmt.Errorf("unmarshall instance value %v", err)
		}

		var containers glue.Containers
		if err := yaml.Unmarshal([]byte(ins.InstanceYaml), &containers); err != nil {
			return fmt.Errorf("unmarshall instance yaml value %v", err)
		}

		for k, v := range containers.Containers {
			memorySize, _ := strconv.ParseInt(v.Memory, 10, 64)
			cpushare, _ := strconv.ParseInt(v.CpuShare, 10, 64)
			cpuSize, _ := strconv.ParseFloat(v.Cpu, 64)
			ccq := v2.CreateContainerReq{
				Guid:          strings.Join([]string{instance.Guid, k}, "_"),
				ContainerName: k,
				ImageTag:      v.ImageTag,
				Runtime:       v.Runtime,
				Workdir:       v.Workdir,
				Cmds:          v.Cmds,
				Envs:          v.Envs,
				Ports:         v.Ports,
				Mounts:        v.Mounts,
				CpuSet:        v.CpuSet,
				Memory:        memorySize,
				CpuShare:      cpushare,
				Cpu:           cpuSize,
				ServicPerfix:  v.ReportProxyServicPerfix,
				ReportEnable:  v.ReportEnable,
			}

			bts, _ := json.Marshal(ccq)

			// call agent to run container
			if _, err := rpc.Rpc.Request(strings.Join([]string{smaps[k], apis.RPCContainerCreate}, ":"), bts); err != nil {
				return fmt.Errorf("nat rpc request %v", err)
			}

			// save the metrics
			infos := map[string]string{
				"name":       k,
				"image":      v.ImageTag,
				"guid":       instance.Guid,
				"platform":   apis.PlatfromGluenets,
				"agent_guid": smaps[k],
				"service":    v.Service,
				"report":     v.ReportProxyServicPerfix,
			}
			updateInfo(instance.Guid, k, smaps[k], apis.PlatfromGluenets, infos, rpc)
		}

		if ins.ConfigKind == apis.KindStrategy {
			rpc.DataBase.StrategyLinksPut(&meta.StrategyLinks{
				StrategyGuid:     instance.Guid,
				ApplicationGuids: meta.EnCodeLinks([]string{}),
			})
		}
		return nil
	case apis.PlatfromKubernetes:
		key := strings.Replace(instance.Label, "resource.helm.instance.application.", "", -1)
		key = strings.Replace(key, ".", "/", 1)

		req := v2.CreateHelmReq{
			Guid:        instance.Guid,
			ReleaseName: instance.Name,
			ChartName:   key,
		}

		bts, _ := json.Marshal(req)

		respBts, err := rpc.Rpc.Request(strings.Join([]string{smaps[instance.Guid], apis.RPCHelmInstallChartTopic}, ":"), bts)
		if err != nil {
			return fmt.Errorf("nat rpc request %v", err)
		}

		resp := v2.CommonResp{}
		if err = json.Unmarshal(respBts, &resp); err != nil {
			return fmt.Errorf("unmarshall %v", err)
		}
		if !resp.IsSuccess {
			return fmt.Errorf("req error %v", resp.Message)
		}
		return nil
	default:
		return fmt.Errorf("unknown mode %v, must in %v", instance.Platform, apis.Platforms())
	}
}

func stopInstance(instance *meta.Instance, smaps map[string]string, rpc *apis.StorageContext) error {
	switch instance.Platform {
	case apis.PlatfromGluenets:
		for k, v := range smaps {
			ccq := v2.CommonDeleteReq{
				Guid: strings.Join([]string{instance.Guid, k}, "_"),
			}

			bts, _ := json.Marshal(ccq)
			if _, err := rpc.Rpc.Request(strings.Join([]string{v, apis.RPCContaienrDelete}, ":"), bts); err != nil {
				return fmt.Errorf("nat rpc request %v", err)
			}
		}
		return nil
	case apis.PlatfromKubernetes:
		req := v2.CommonDeleteReq{
			Guid: instance.Guid,
		}

		bts, _ := json.Marshal(req)
		respBts, err := rpc.Rpc.Request(strings.Join([]string{smaps[instance.Guid], apis.RPCHelmUnInstallRealseTopic}, ":"), bts)
		if err != nil {
			return fmt.Errorf("nat rpc request %v", err)
		}
		resp := v2.CommonResp{}
		if err = json.Unmarshal(respBts, &resp); err != nil {
			return fmt.Errorf("unmarshall %v", err)
		}
		if !resp.IsSuccess {
			return fmt.Errorf("req error %v", resp.Message)
		}

		return nil
	default:
		return fmt.Errorf("unknown mode %v, must in %v", instance.Platform, apis.Platforms())
	}
}

func updateInfo(appGuid, container, agentGuid, plarform string, info map[string]string, c *apis.StorageContext) {
	// save the metrics
	key := strings.Join([]string{apis.PlatfromGluenets, "agent_containers", agentGuid}, "|")
	infosi, _ := c.InfoFlusher.Info.LoadOrStore(key, make(map[string]string))
	infosg := infosi.(map[string]string)
	infosg[container] = appGuid

	key2 := strings.Join([]string{apis.PlatfromGluenets, "instance_containers", appGuid}, "|")
	infosi2, _ := c.InfoFlusher.Info.LoadOrStore(key2, make(map[string]string))
	infosg2 := infosi2.(map[string]string)
	infosg2[container] = agentGuid

	key3 := strings.Join([]string{apis.PlatfromGluenets, "container_info", appGuid}, "|")
	infosi3, _ := c.InfoFlusher.Info.LoadOrStore(key3, make(map[string]map[string]string))
	infosg3 := infosi3.(map[string]map[string]string)
	infosg3[container] = info

	key4 := strings.Join([]string{plarform, "container_state", appGuid, container}, "|")
	data := map[string]interface{}{
		"object":   "application",
		"platform": plarform,
		"time":     time.Now().Unix(),
	}
	data["value"] = meta.StateNotReady
	data["label"] = "container_state"
	data["guid"] = appGuid
	data["container"] = container

	var sub *sync.Map
	subi, _ := c.StatusSubscriber.LoadOrStore(key4, &sync.Map{})
	sub = subi.(*sync.Map)
	bts, _ := json.Marshal(data)
	str := string(bts)
	sub.Range(func(k, v interface{}) bool {
		mch := v.(chan string)
		mch <- str
		return true
	})
	c.StatusSubscriberTotal.Range(func(k, v interface{}) bool {
		mch := v.(chan string)
		mch <- str
		return true
	})

	key5 := strings.Join([]string{plarform, "container_state", appGuid}, "|")
	infosi, ok := c.InfoFlusher.Info.LoadOrStore(key5, make(map[string]string))
	infos := infosi.(map[string]string)
	infos[container] = meta.StateNotReady
	if !ok {
		c.InfoFlusher.Func.Store(key5, func(k, v interface{}) error {
			guid := strings.Split(k.(string), "|")[2]
			detail, i := make([]string, len(v.(map[string]string))), 0
			for ct, st := range v.(map[string]string) {
				detail[i] = strings.Join([]string{ct, st}, ",")
				i++
			}
			statusStr := strings.Join(detail, "|")
			if err := c.DataBase.InstanceRuntimeStatusUpdate(guid, statusStr); err != nil {
				c.Logger.Warnf("update %v runtime container %v", guid, err)
			}
			return nil
		})
	}
}
